﻿using FileStorageCenter.Services.Models;
using HelenServer.Modules.FileStorage.Abstractions;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using FileInfo = FileStorageCenter.Services.Models.FileInfo;

namespace FileStorageCenter.Services.Services.Impl;

internal class FileInfoService : IFileInfoService
{
    private readonly DbContext _context;
    private readonly IFileStorageService _fileStorageService;
    private readonly IUploadQueueProvider<UploadModel> _provider;

    public FileInfoService(DbContext context, IFileStorageService fileStorageService, IUploadQueueProvider<UploadModel> provider)
    {
        _context = context;
        _fileStorageService = fileStorageService;
        _provider = provider;
    }

    public async Task<PageResult<FileInfo.QueryModel>> GetPageAsync(FileInfo.ConditionSelectorCondition condition, CancellationToken cancellationToken)
    {
        var result = new PageResult<FileInfo.QueryModel>();

        try
        {
            var expression = GetConditionSelectorCondition(condition);

            var fileInfos = await _context.Set<FileInfo>()
                .Include(x => x.AddedBy)
                .Where(expression)
                .OrderByDescending(x => x.CreatedDate)
                .Skip((condition.PageNum - 1) * condition.PageSize)
                .Take(condition.PageSize)
                .AsNoTracking()
                .ToListAsync(cancellationToken);

            result.Data = fileInfos.Select(x => GetQueryModel(x));

            result.PageNum = condition.PageNum;

            result.PageSize = condition.PageSize;

            result.Total = await _context.Set<FileInfo>().CountAsync(expression, cancellationToken);

            result.Status = OperationalResult.SUCCESS;
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public async Task<OperationalResult> UpdateAsync(int id, FileInfo.UpdateModel fileInfo, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        try
        {
            var entity = await _context.Set<FileInfo>()
                .Where(m => m.Id.Equals(id))
                .FirstOrDefaultAsync(cancellationToken);

            if (entity is null)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";

                return result;
            }

            entity.Name = fileInfo.Name;

            _context.Set<FileInfo>().Update(entity);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;

            result.Message = $"{affectedRows} rows affected.";
        }
        catch (Exception error)
        {
            var exists = await _context.Set<FileInfo>().AnyAsync(m => m.Id.Equals(id), cancellationToken);

            if (!exists)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";
            }
            else
            {
                result.Status = OperationalResult.ERROR;

                result.Message = error.ToString();
            }
        }

        return result;
    }

    public async Task<OperationalResult> DeleteAsync(int id, CancellationToken cancellationToken)
    {
        var result = new OperationalResult();

        try
        {
            var fileInfo = await _context.Set<FileInfo>()
                .Include(x => x.AddedBy)
                .Where(m => m.Id.Equals(id))
                .FirstOrDefaultAsync(cancellationToken);

            if (fileInfo is null)
            {
                result.Status = OperationalResult.FAILURE;

                result.Message = "0 rows affected.";

                return result;
            }

            _context.Set<FileInfo>().Remove(fileInfo);

            var affectedRows = await _context.SaveChangesAsync(cancellationToken);

            result.Status = affectedRows > 0 ? OperationalResult.SUCCESS : OperationalResult.FAILURE;

            result.Message = $"{affectedRows} rows affected.";
        }
        catch (Exception error)
        {
            result.Status = OperationalResult.ERROR;

            result.Message = error.ToString();
        }

        return result;
    }

    public static FileInfo.QueryModel GetQueryModel(FileInfo fileInfo)
    {
        return new FileInfo.QueryModel
        {
            Id = fileInfo.Id,
            CreatedDate = fileInfo.CreatedDate,
            LastModifiedDate = fileInfo.LastModifiedDate,
            FileId = fileInfo.FileId,
            Url = fileInfo.Url,
            GroupName = fileInfo.GroupName,
            Name = fileInfo.Name,
            Extension = fileInfo.Extension,
            Size = fileInfo.Size,
            AddedBy = UserService.GetQueryModel(fileInfo.AddedBy),
            Hash = fileInfo.Hash,
        };
    }

    private static Expression<Func<FileInfo, bool>> GetConditionSelectorCondition(FileInfo.ConditionSelectorCondition condition)
    {
        Expression<Func<FileInfo, bool>>? expression = null;

        if (condition.Keywords is not null)
        {
            Expression<Func<FileInfo, bool>>? keywords = null;
            keywords = fileInfo => fileInfo.GroupName.Contains(condition.Keywords);
            keywords = keywords.Or(fileInfo => fileInfo.Name.Contains(condition.Keywords));
            keywords = keywords.Or(fileInfo => fileInfo.Extension.Contains(condition.Keywords));
            keywords = keywords.Or(fileInfo => fileInfo.AddedBy.Name.Contains(condition.Keywords));
            expression = expression?.And(keywords) ?? keywords;
        }

        return expression ?? (fileInfo => true);
    }

    public async ValueTask UploadFileAsync(UploadModel model, CancellationToken cancellationToken = default)
    {
        model.AddedBy = await CurrentUserAsync(cancellationToken);

        _provider.Enqueue(model);
    }

    public async ValueTask UploadFileRangeAsync(IEnumerable<UploadModel> models, CancellationToken cancellationToken = default)
    {
        var user = await CurrentUserAsync(cancellationToken);

        var copy = models.ToList();
        copy.ForEach(x => x.AddedBy = user);

        models = copy.AsEnumerable();

        _provider.EnqueueRange(models);
    }

    public async ValueTask<OperationalResult<DownloadModel>> DownloadFileAsync(string fileid, CancellationToken cancellationToken = default)
    {
        return await _fileStorageService.DownloadFileAsync(fileid, cancellationToken);
    }

    public async ValueTask<OperationalResult<bool>> RemoveFileAsync(string fileid)
    {
        return await _fileStorageService.RemoveFileAsync(fileid);
    }

    private async Task<int> CurrentUserAsync(CancellationToken cancellationToken = default)
    {
        return await _context.Set<User>().Select(x => x.Id).FirstAsync(cancellationToken);

        var account = App.HttpContext!.User.Claims.FirstOrDefault(n => n.Type is "name" or ClaimTypes.Name)!.Value;

        var entity = await _context.Set<User>().FirstOrDefaultAsync(x => x.Account == account, cancellationToken);

        if (entity is null)
        {
            var user = new User
            {
                Account = account,
                Name = "ExternalUser",
                Password = App.Encrypt("**")
            };

            await _context.Set<User>().AddAsync(user, cancellationToken);

            await _context.SaveChangesAsync(cancellationToken);

            entity = user;
        }

        //return entity;
    }

    public async Task ChunkStageAsync(ChunkModel model, CancellationToken cancellationToken = default)
    {
        var target = Path.Combine(Path.GetTempPath(), ".chunks", model.Identifier);

        if (!Directory.Exists(target))
        {
            Directory.CreateDirectory(target);
        }

        var chunkPath = Path.Combine(target, model.ChunkName);

        if (File.Exists(chunkPath))
        {
            return;
        }

        using var file = File.Create(chunkPath);

        await model.Buffer.CopyToAsync(file, cancellationToken);
    }

    public async Task ChunkMergeAsync(string identifier, string filename, CancellationToken cancellationToken = default)
    {
        var target = Path.Combine(Path.GetTempPath(), ".chunks", identifier);

        var index = target.LastIndexOf('\\') + 1;

        var chunkPaths = Directory
            .GetFiles(target)
            .Where(x => string.IsNullOrEmpty(Path.GetExtension(x)))
            .OrderBy(x => int.Parse(x[x.LastIndexOf('\\')..][1..]));

        using var file = File.Create(Path.Combine(target, filename));

        foreach (var chunk in chunkPaths)
        {
            using var chunkFile = File.OpenRead(chunk);

            await chunkFile.CopyToAsync(file, cancellationToken);
        }

        await file.FlushAsync(cancellationToken);

        var model = new UploadModel
        {
            AddedBy = await CurrentUserAsync(cancellationToken),
            Name = filename,
            Extension = Path.GetExtension(filename),
            Buffer = file,
            Hash = identifier,
        };

        _provider.Enqueue(model);

        foreach (var chunk in chunkPaths)
        {
            File.Delete(chunk);
        }
    }

    public async Task<OperationalResult<bool>> ChunkVerifyAsync(string identifier, string chunkIndex, CancellationToken cancellationToken = default)
    {
        var result = new OperationalResult<bool>();

        var exists = await _context.Set<FileInfo>().FirstOrDefaultAsync(x => x.Hash == identifier, cancellationToken);

        if (exists is null)
        {
            var target = Path.Combine(Path.GetTempPath(), ".chunks", identifier);

            result.Data = File.Exists(Path.Combine(target, chunkIndex));

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "秒传";

        return result;
    }

    public async Task<OperationalResult<IEnumerable<int>>> GetChunkSlicesAsync(string identifier, CancellationToken cancellationToken = default)
    {
        var result = new OperationalResult<IEnumerable<int>>();

        var exists = await _context.Set<FileInfo>().FirstOrDefaultAsync(x => x.Hash == identifier, cancellationToken);

        if (exists is null)
        {
            var target = Path.Combine(Path.GetTempPath(), ".chunks", identifier);

            try
            {
                result.Data = Directory
                .GetFiles(target)
                .Where(x => string.IsNullOrEmpty(Path.GetExtension(x)))
                .Select(x => int.Parse(x[x.LastIndexOf('\\')..][1..]));
            }
            catch (DirectoryNotFoundException)
            {
                result.Data = Enumerable.Empty<int>();
            }

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "秒传";

        return result;
    }
}